xdim
Multi-Dimensional Functions
motivation
I work a lot with satellite imagery. In theory, most satellite imagery has three dimensions: (1) band, (2) row, and (3) column. However, for practical reasons, this data is often structured in a flat array, like ImageData.data or a two-dimensional array where each subarray holds all the values for a specific band in row-major order. This library was created for two main purposes: (1) to provide a unified interface for querying this data regardless of its practical structure and (2) converting this data between different array layouts.
install
npm install xdim
xdim layout syntax
Most of the functions in this library require that you specify the layout of the data using "xdim layout syntax" or "xdim syntax" for short. The format is simple with just a few main pieces:
- The straight brackets
[
and ]
indicates an actual array or subarrays. - The comma
,
appears between [
and ]
and means dimensions are interleaved in left-to-right major order. - Dimension names can be made of any letter A to Z, lowercased or uppercased, can include underscores, and don't include spaces.
xdim layout syntax examples
Here's a couple examples of the "xdim layout syntax":
example: cars
You have an array of information about car models where the information is stored in subarrays:
[
["Fusion", "Ford", "United States", "2005", "2020"]
["Versa", "Nissan", "Japan", "2006", "2021"]
]
The layout could be described as "[model][brand,maker,county,start_year,end_year]"
example: pixels
You have ImageData.data:
[31, 9, 71, 255, 126, 42, 53, 255, 71, 74, 71, 255, ...]
The layout could be described as "[row,column,band]"
.
usage
This library provides the following functions: select, prepareSelect, clip, iterClip, transform, prepareData, update, and prepareUpdate.
select
Select is used to get the value at a given multi-dimensional point. The point is an object where each key is the name of a dimension with an index number. Index numbers start at zero and increase until we reach the end of the length in the dimension.
import { select } from 'xdim';
const data = [
[0, 123, 123, 162, ...],
[213, 41, 62, 124, ...],
[84, 52, 124, 235, ...]
];
const result = select({
data,
layout: "[band][row,column]",
sizes: {
band: 3,
column: 100
},
point: {
band: 2,
row: 74,
column: 63
}
});
result is an object
{
value: 62,
index: 7463,
parent: [84, 52, 124, 235, ... 62, ...]
}
prepareSelect
The prepareSelect
function is use to create a supercharged select function for some data. There is some
fixed cost to creating the function, so only use it if you think you will run several to many selects.
:sparkles: So what magic makes the prepared select statements so fast? We pre-generate
select functions, so that JavaScript compilers
can optimize the logical steps needed to lookup data. We then just bind the dimension names, sizes, and data to these "pre-compiled" functions.
import { prepareSelect } from 'xdim';
const data = [
[0, 123, 123, 162, ...],
[213, 41, 62, 124, ...],
[84, 52, 124, 235, ...]
];
const select = prepareSelect({
data,
layout: "[band][row,column]",
sizes: {
band: 3,
column: 100
}
});
const result = select({
point: {
band: 2,
row: 74,
column: 63
}
});
result is an object
{
value: 62,
index: 7463,
parent: [84, 52, 124, 235, ... 62, ...]
}
clip
The clip
function is used to pull out a subsection of the data within a hyperrectangle (i.e. multi-dimensional rectangle), which we call "rect". The "rect" is defined by an object with dimension name keys and a numerical range. The range is "inclusive", including the first and last numbers provided.
import { clip } from 'xdim';
const data = [
[0, 123, 123, 162, ...],
[213, 41, 62, 124, ...],
[84, 52, 124, 235, ...]
];
const result = clip({
data,
flat: false,
layout: "[band][row,column]",
sizes: {
band: 3,
column: 100
},
rect: {
band: [2, 2],
row: [55, 74],
column: [60, 62]
}
});
result is an object
{
data: [
[64, 27, 19, 23, 45, 82 ... ]
]
}
iterClip
Like clip, but returns a flat iterator of values. Useful if you want to minimize memory usage and avoid creating a new array.
import { iterClip } from 'xdim';
const data = [
[0, 123, 123, 162, ...],
[213, 41, 62, 124, ...],
[84, 52, 124, 235, ...]
];
const result = iterClip({
data,
layout: "[band][row,column]",
sizes: {
band: 3,
column: 100
},
rect: {
band: [2, 2],
row: [55, 74],
column: [60, 62]
},
order: ["band", "row", "column"]
});
result is an iterator object
result.next();
for (let n of result) {
}
transform
If your data is a one dimensional array, you can transform to another using the transform function.
In the example below we transform from a flat array of ImageData.data to a truly 3-dimensional array of arrays of arrays representing bands, then rows, then columns.
import { transform } from 'multdimensional-functions';
const data = [0, 213, 84, 255, 123, 41, 52, 255, 123, 62, 124, 255, 162, 124, 235, 255, ...];
const result = transform({
data,
from: "[row,column,band]",
to: "[band][row][column]",
sizes: {
band: 4,
row: 768,
column: 1024
}
});
result is an object
{
data: [
[
[0, 123, 123, 162, ...]
[212, 124, 127, 92, ... ]
],
[
[ ... ],
[ ... ]
],
[
[ ... ],
[ ... ]
],
[
[ ... ],
[ ... ]
]
]
}
prepareData
If you just want to create the outline or skeleton of your structure without filling it in with data, you can call the prepareData function.
import { prepareData } from 'xdim';
const result = prepareData({
fill: -99,
layout: "[band][row][column]",
sizes: {
band: 4,
column: 1024,
row: 768
},
arrayTypes: ["Array", "Array", "Int8Array"]
});
Result is an object with an empty data object and shape array. The data object holds the multi-dimensional array of arrays.
The shape array is an array that describes the actual length of the arrays used to hold the data. (It is the actual practical length and not the theoretical length of the dimensions).
{
shape: [4, 768, 1024], // describes the actual length of each array
data: [
// first band
[
Int8Array[-99, -99, ... ], // band's first row of columns with length being the number of columns
Int8Array[-99, -99, ... ], // band's second row
.
.
.
],
// second band
[
Int8Array[-99, -99, ... ], // band's first row of columns with length being the number of columns
Int8Array[-99, -99, ... ], // band's second row
]
]
update
If you have a multi-dimensional data structure and want to change a value, use update
.
import { update } from 'xdim';
const data = [128, 31, 382, 255, 48, 38, 58, 255, ...];
update({
data,
layout: "[row,column,band]",
point: {
band: 2,
row: 4,
column: 8,
},
sizes: {
band: 4,
row: 768,
column: 1024,
},
value: 128
});
prepareUpdate
The function prepareUpdate
is to update as prepareSelect is to select. It returns an optimized update function.
import { prepareUpdate } from 'xdim';
const data = [128, 31, 382, 255, 48, 38, 58, 255, ...];
const update = prepareUpdate({
data,
layout: "[row,column,band]",
sizes: {
band: 4,
row: 768,
column: 1024,
}
});
update({
point: {
band: 2,
row: 4,
column: 8,
},
value: 128
});
used by
support
Post an issue at https://github.com/DanielJDufour/xdim/issues.